package com.mofum.msdom.excel.hander.poi;

import com.mofum.msdom.excel.constant.ExcelConfig;
import com.mofum.msdom.excel.constant.RowContext;
import com.mofum.msdom.excel.converter.TypeConverter;
import com.mofum.msdom.excel.exception.NumberVerificationException;
import com.mofum.msdom.excel.filter.DataFilter;
import com.mofum.msdom.excel.metadata.MPColumn;
import com.mofum.msdom.excel.metadata.MPHeaderRow;
import com.mofum.msdom.excel.metadata.MPSheet;
import com.mofum.msdom.excel.parser.ExcelParserCallback;
import com.mofum.msdom.excel.utils.MetadataConvertUtils;
import com.mofum.msdom.excel.utils.ReflectUtils;
import com.mofum.msdom.excel.validator.Validator;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.usermodel.XSSFComment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.util.Map;

/**
 * 表格内容 类型解析器
 *
 * @author 1615690513@qq.com
 * @since 2018/11/21 0021 16:27
 */
public class TypeContentHandler<T, P extends ExcelParserCallback<T>> implements XSSFSheetXMLHandler.SheetContentsHandler {

    public static Logger logger = LoggerFactory.getLogger(TypeContentHandler.class);

    /**
     * 数据类型
     */
    protected Class<T> dataType;

    /**
     * 数据内容
     */
    protected T data = null;

    /**
     * 列Map
     */
    protected Map<Field, MPColumn> columnMap;

    /**
     * 字段Map
     */
    protected Map<Integer, String> indexMap;

    /**
     * 转换器Map
     */
    protected Map<Field, TypeConverter> converterMap;

    /**
     * 校验器Map
     */
    protected Map<Field, Validator> validatorMap;

    /**
     * 工作表
     */
    protected MPSheet[] sheets;

    /**
     * 数据过滤器Map
     */
    protected Map<Integer, DataFilter[]> dataFilterMap;

    /**
     * 解析完毕后的回调
     */
    protected P callback;

    /**
     * 界限
     */
    protected int limit = 0;

    /**
     * 偏移量
     */
    protected int offset = 0;

    /**
     * 工作表
     */
    protected MPSheet mpSheet;

    /**
     * 需要表头
     */
    protected boolean needHead = false;

    /**
     * Excel配置
     */
    private ExcelConfig excelConfig;

    /**
     * 解析一行数据的开始
     *
     * @param i 行号
     */
    public void startRow(int i) {
        if (skipHead(i)) {
            return;
        }

        if (!existsRange(i)) {
            return;
        }

        //解析开始 ---->>>>>>>>>>>>>>>>>>

        logger.debug("正在解析" + i + "行。");

        try {

            data = dataType.newInstance();

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    private boolean skipHead(int i) {
        //过滤头部
        if (!needHead) {
            MPSheet mpSheet = sheets[this.mpSheet.getIndex()];
            MPHeaderRow[] mpHeaderRows = mpSheet.getHeaderRows();
            if (mpHeaderRows != null) {
                for (MPHeaderRow headerRow : mpHeaderRows) {
                    if (headerRow.getIndex() == i) {

                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 解析完一行数据后，调用此方法
     *
     * @param i
     */
    public void endRow(int i) {
        if (skipHead(i)) {
            return;
        }

        if (!existsRange(i)) {
            return;
        }

        //空行数据快进
        if (isNullRow(dataType, data)) {
            return;
        }

        if (mpSheet != null) {

            DataFilter defaultFiter = ReflectUtils.newInstance(getExcelConfig().getFilter());

            if (defaultFiter != null) {
                if (isNeedFilter(defaultFiter, data, i, mpSheet.getIndex())) {
                    return;
                }
            }

            DataFilter[] dataFilters = dataFilterMap.get(mpSheet.getIndex());

            if (dataFilters != null) {
                for (DataFilter dataFilter : dataFilters) {
                    if (isNeedFilter(dataFilter, data, i, mpSheet.getIndex())) {
                        return;
                    }
                }
            }
        }


        //检查必填字段
        try {

            Integer sheetIndex = 0;
            if (mpSheet != null) {
                sheetIndex = mpSheet.getIndex();
            }


            //数据验证失败跳过此条数据
            if (!verificationField(data, i, sheetIndex)) {
                return;
            }

            verificationRequiredFieldNotNull(dataType, data);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new NumberVerificationException(e.getMessage(), e, i);
        }


        try {

            RowContext<T> context = new RowContext<>();

            context.setRowIndex(i);

            context.setSheetIndex(mpSheet.getIndex());

            context.setData(data);

            callback.parseDataAfter(context);

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
        logger.debug(i + "行。" + "解析完毕");
        //解析结束 ----<<<<<<<<<<<<<<<<<<<<<<
    }

    /**
     * 是否需要过滤
     *
     * @param dataFilter 数据过滤器
     * @param data       数据
     * @param row        行号
     * @param sheetIndex 当前Sheet编号
     */
    private boolean isNeedFilter(DataFilter dataFilter, T data, int row, int sheetIndex) {
        RowContext rowContext = new RowContext();
        rowContext.setData(data);
        rowContext.setRowIndex(row);
        rowContext.setSheetIndex(sheetIndex);
        return dataFilter.execute(rowContext);
    }

    /**
     * 验证逻辑
     *
     * @param data       数据
     * @param row        行号
     * @param sheetIndex 当前Sheet编号
     */
    private boolean verificationField(T data, int row, int sheetIndex) throws Exception {
        if (validatorMap == null) {
            return true;
        }
        boolean flag = true;
        for (Field field : validatorMap.keySet()) {
            Validator validator = validatorMap.get(field);
            RowContext rowContext = new RowContext();
            rowContext.setData(data);
            rowContext.setRowIndex(row);
            rowContext.setSheetIndex(sheetIndex);
            flag = validator.doCheckData(rowContext);
            if (!flag) {
                return flag;
            }
        }
        return true;
    }

    /**
     * 判断空行数据
     *
     * @param dataType 数据类型
     * @param data     数据
     * @throws RuntimeException
     */
    private boolean isNullRow(Class<T> dataType, T data) throws RuntimeException {
        for (Field field : dataType.getDeclaredFields()) {
            Object value = null;
            try {
                field.setAccessible(true);
                value = field.get(data);
                field.setAccessible(false);
                if (value != null) {
                    return false;
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        return true;
    }

    /**
     * 检查字段是否为空
     */
    private void verificationRequiredFieldNotNull(Class<T> dataType, T data) throws RuntimeException {
        for (Field field : dataType.getDeclaredFields()) {
            if (verificationRequiredField(field)) {
                Object value = null;
                try {
                    field.setAccessible(true);
                    value = field.get(data);
                    field.setAccessible(false);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
                if (value == null) {
                    throw new RuntimeException("Type：" + data.getClass() + "Field(" + field.getName() + ") must be required！");
                }
            }
        }
    }


//    private void clearData(T data) throws IllegalAccessException {
//        Field[] fields = data.getClass().getDeclaredFields();
//
//        for (Field field : fields) {
//            field.set(data, null);
//        }
//
//    }

    /**
     * 解析单元格类容
     *
     * @param cellReference  单元格引用地址
     * @param formattedValue 格式化的内容
     * @param comment        注释内容
     */
    public void cell(String cellReference, String formattedValue, XSSFComment comment) {
        CellReference reference = new CellReference(cellReference);
        if (skipHead(reference.getRow())) {
            return;
        }
        if (!existsRange(reference.getRow())) {
            reference = null;
            return;
        }
        int colIndex = reference.getCol();
        convertColumn(colIndex, formattedValue);

    }

    /**
     * 转换列
     *
     * @param colIndex
     * @param formattedValue
     */
    private void convertColumn(int colIndex, String formattedValue) {

        //根据列名获得字段名称
        Field field = getFiledByIndex(colIndex);

        if (field == null) {
            return;
        }

        //根据设置的转换器设置值
        convert(field, formattedValue);
    }

    private boolean verificationRequiredField(Field field) {
        if (getColumnMap() != null) {
            MPColumn column = getColumnMap().get(field);
            if (column == null) {
                return false;
            }
            return column.isRequired();
        }
        return false;
    }

    /**
     * i 是否存在指定的区间
     *
     * @param i
     */
    private boolean existsRange(int i) {

        int row = i + 1;

        int rule = limit + offset;
        // i ∈ ( 0 ，+ ∞ )
        if (rule == 0) {
            return true;
        }

        // i ∈ ( 0 , limit + offset)
        if (limit == 0 && rule > 0) {
            return row <= limit + offset;
        }

        // i ∈ (limit , + ∞)
        if (offset == 0 && rule > 0) {
            return row > limit;
        }

        //  i ∈ (  limit , limit + offset )
        return (row <= limit + offset) && row > limit;

    }

    /**
     * 转换值
     *
     * @param field          字段
     * @param formattedValue 值
     */
    private void convert(Field field, String formattedValue) {

        try {

            TypeConverter converter = getConverterByField(field);
            field.setAccessible(true);
            field.set(data, converter.convert(formattedValue, field.getType()));
            field.setAccessible(false);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 获得类型转换器
     *
     * @param field 字段
     */
    private TypeConverter getConverterByField(Field field) {

        TypeConverter converter = null;

        try {

            converter = converterMap.get(field);

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }

        return converter;
    }

    /**
     * 根据列索引获得字段
     *
     * @param colIndex 列索引
     */
    private Field getFiledByIndex(int colIndex) {
        Field field = null;
        try {

            String name = getFiledNameByIndex(colIndex);

            if (name == null) {
                return null;
            }

            field = dataType.getDeclaredField(getFiledNameByIndex(colIndex));

        } catch (NoSuchFieldException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
        return field;
    }

    /**
     * 根据列索引获得字段名称
     *
     * @param colIndex 字段索引
     */
    private String getFiledNameByIndex(int colIndex) {
        String result = null;
        try {
            result = indexMap.get(colIndex);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
        return result;
    }


    private Map<Field, MPColumn> convertStringToColumn(Class<T> dataType, Map<String, Integer> columnMap) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        return MetadataConvertUtils.convertStringToColumn(dataType, columnMap);
    }

    protected Map<Field, TypeConverter> columnMapToConverterMap(Map<Field, MPColumn> columnMap, Class<? extends TypeConverter> defaultConvert) throws IllegalAccessException, InstantiationException {
        return MetadataConvertUtils.columnMapToConverterMap(columnMap, defaultConvert);
    }

    protected Map<Field, Validator> columnMapTValidatorMap(Map<Field, MPColumn> columnMap) throws IllegalAccessException, InstantiationException {
        return MetadataConvertUtils.columnMapToValidatorMap(columnMap, getExcelConfig().getValidator());
    }

    protected Map<Integer, DataFilter[]> sheetMapToDataFilterMap(Map<Integer, MPSheet> sheetMap) throws IllegalAccessException, InstantiationException {
        return MetadataConvertUtils.sheetMapToDataFilterMap(sheetMap);
    }

    protected Map<Integer, MPSheet> sheetArrayToDataFilterMap(MPSheet[] sheets) throws IllegalAccessException, InstantiationException {
        return MetadataConvertUtils.sheetArrayToDataFilterMap(sheets);
    }

    /**
     * 字段Map转索引Map
     *
     * @param columnMap 字段Map值
     */
    protected Map<Integer, String> columnMapToIndexMap(Map<Field, MPColumn> columnMap) {
        return MetadataConvertUtils.columnMapToIndexMap(columnMap);
    }


    public Class<T> getDataType() {
        return dataType;
    }

    public void setDataType(Class<T> dataType) {
        this.dataType = dataType;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public Map<Field, MPColumn> getColumnMap() {
        return columnMap;
    }

    public void setColumnMap(Map<Field, MPColumn> columnMap) {
        this.columnMap = columnMap;
    }

    public Map<Integer, String> getIndexMap() {
        return indexMap;
    }

    public void setIndexMap(Map<Integer, String> indexMap) {
        this.indexMap = indexMap;
    }

    public P getCallback() {
        return callback;
    }

    public void setCallback(P callback) {
        this.callback = callback;
    }

    public int getLimit() {
        return limit;
    }

    public void setLimit(int limit) {
        this.limit = limit;
    }

    public int getOffset() {
        return offset;
    }

    public void setOffset(int offset) {
        this.offset = offset;
    }

    public Map<Field, TypeConverter> getConverterMap() {
        return converterMap;
    }

    public void setConverterMap(Map<Field, TypeConverter> converterMap) {
        this.converterMap = converterMap;
    }

    public Map<Field, Validator> getValidatorMap() {
        return validatorMap;
    }

    public void setValidatorMap(Map<Field, Validator> validatorMap) {
        this.validatorMap = validatorMap;
    }

    public Map<Integer, DataFilter[]> getDataFilterMap() {
        return dataFilterMap;
    }

    public void setDataFilterMap(Map<Integer, DataFilter[]> dataFilterMap) {
        this.dataFilterMap = dataFilterMap;
    }

    public MPSheet getMpSheet() {
        return mpSheet;
    }

    public void setMpSheet(MPSheet mpSheet) {
        if (mpSheet != null) {
            setLimit(mpSheet.getLimit());
            setOffset(mpSheet.getOffset());
        }
        this.mpSheet = mpSheet;
    }

    public MPSheet[] getSheets() {
        return sheets;
    }

    public void setSheets(MPSheet[] sheets) {
        this.sheets = sheets;
    }

    public boolean isNeedHead() {
        return needHead;
    }

    public void setNeedHead(boolean needHead) {
        this.needHead = needHead;
    }

    public TypeContentHandler() {
    }

    public TypeContentHandler(MPSheet[] mpSheets, T t, Map<String, Integer> columnMap, P callback) {
        try {
            Class<T> tClass = (Class<T>) t.getClass();
            this.columnMap = convertStringToColumn(tClass, columnMap);
            this.indexMap = columnMapToIndexMap(this.columnMap);
            this.converterMap = columnMapToConverterMap(this.columnMap, getExcelConfig().getConverter());
            this.validatorMap = columnMapTValidatorMap(this.columnMap);

        } catch (Exception e) {

            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);

        }
        this.callback = callback;
        this.sheets = mpSheets;
        try {
            this.dataFilterMap = sheetMapToDataFilterMap(sheetArrayToDataFilterMap(this.sheets));
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public TypeContentHandler(MPSheet[] mpSheets, Class<T> dataType, Map<Field, MPColumn> columnMap, P callback) {
        this.dataType = dataType;
        this.columnMap = columnMap;
        this.indexMap = columnMapToIndexMap(this.columnMap);
        try {

            this.converterMap = columnMapToConverterMap(this.columnMap, getExcelConfig().getConverter());
            this.validatorMap = columnMapTValidatorMap(this.columnMap);
        } catch (Exception e) {

            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);

        }
        this.sheets = mpSheets;
        this.callback = callback;
        try {
            this.dataFilterMap = sheetMapToDataFilterMap(sheetArrayToDataFilterMap(this.sheets));
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public TypeContentHandler(MPSheet[] mpSheets, Class<T> dataType, Map<Field, MPColumn> columnMap, Map<Integer, String> indexMap, Map<Field, TypeConverter> converterMap, P callback) {
        this.dataType = dataType;
        this.columnMap = columnMap;
        this.indexMap = indexMap;
        this.converterMap = converterMap;
        this.callback = callback;
        this.sheets = mpSheets;
        try {
            this.dataFilterMap = sheetMapToDataFilterMap(sheetArrayToDataFilterMap(this.sheets));
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public ExcelConfig getExcelConfig() {
        if (excelConfig == null) {
            return ExcelConfig.DEFAULT_EXCEL_CONFIG;
        }
        return excelConfig;
    }

    public void setExcelConfig(ExcelConfig excelConfig) {
        this.excelConfig = excelConfig;
    }
}
